/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse.query;

import java.io.*;

import nfse.*;
import nfse.result.*;

import org.xml.sax.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;
import java.util.Vector;

public class QueryHandler extends DefaultHandler {
    private XMLReader xr = null;

    private Query query;

    private QueryServer queryServer = null;

    Vector<ResultField> resultFields = new Vector<ResultField>();

    boolean foundError = false;

    public QueryHandler(QueryServer queryServer) {
        try {
            this.queryServer = queryServer;
            xr = XMLReaderFactory.createXMLReader();
            xr.setContentHandler(this);
            xr.setErrorHandler(this);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Query getQuery() {
        if (!foundError)
            return this.query;
        return null;
    }

    public void parse(InputStream in) {
        try {
            String query = "";
            LineNumberReader r = new LineNumberReader(new InputStreamReader(in));
            String line = r.readLine();
            while (line != null) {
                System.out.println(line);
                int termIdx = line.indexOf("<--DiSARM: end query-->");
                if (termIdx == 0) {
                    break;
                } else if (termIdx > 0) {
                    query += line.substring(0, termIdx);
                    break;
                } else {
                    query += line;
                }
                /*
                 * if (line.startsWith(" <--NetFSE: end query-->")) break; else
                 * query += line;
                 */
                line = r.readLine();
            }
            System.out.println("Parsing query: '" + query + "'");
            StringReader str = new StringReader(query);
            xr.parse(new InputSource(str));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
     * private void process() { //System.out.println("Parsing the query...");
     * //System.out.println(query.toXML()); //System.out.println("...done."); }
     */
    ////////////////////////////////////////////////////////////////////
    // Event handlers.
    ////////////////////////////////////////////////////////////////////
    public void startDocument() {
        //System.out.println("Start of query");
        //query = new Query();
    }

    public void endDocument() {
        //System.out.println("End of query");
        //process();
    }

    public void startElement(String uri, String name, String qName, Attributes atts) {
        try {
            if ("".equals(uri)) {
                if (qName.compareTo("Query") == 0) {
                    boolean hadID = false;
                    if (query == null) { // this is the top of the query tree
                        query = new Query(queryServer.nextQueryID());
                    } else { // this is a subquery, set it's parent to the
                        // current query
                        Query temp = query;
                        query = new Query(queryServer.nextQueryID());
                        query.setParent(temp);
                    }
                    int sensor = -1;
                    int type = NetFSE.TYPE_ALL;
                    for (int i = 0; i < atts.getLength(); i++) {
                        String attrName = atts.getLocalName(i);
                        String attrValue = atts.getValue(i);
                        if (attrName.compareTo("date") == 0) {
                            String[] dates = attrValue.split("~");
                            if (dates.length == 2) {
                                DateRange dr = new DateRange(QueryDate.processDate(dates[0]));
                                dr.setEndDate(QueryDate.processDate(dates[1]));
                                query.addDateRange(dr);
                            } else {
                                query.addDate(QueryDate.processDate(attrValue));
                            }
                        } else if (attrName.compareTo("user") == 0) {
                            query.setUser(attrValue);
                        } else if (attrName.compareTo("password") == 0) {
                            query.setPassword(attrValue);
                        } else if (attrName.compareTo("userip") == 0) {
                            query.setUserIP(attrValue);
                        } else if (attrName.compareTo("sessionid") == 0) {
                            query.setSessionID(attrValue);
                        } else if (attrName.compareTo("id") == 0) {
                            hadID = true;
                        } else if (attrName.compareTo("distribute") == 0) {
                            if (attrValue.toUpperCase().compareTo("TRUE") == 0) {
                                query.setDistribute(true);
                            } else {
                                query.setDistribute(false);
                            }
                        } else if (attrName.compareTo("type") == 0) {
                            type = Integer.parseInt(attrValue);
                        } else if (attrName.compareTo("sensor") == 0) {
                            sensor = Integer.parseInt(attrValue);
                        } else {
                            int field = NetFSE.getFieldType(attrName);
                            if (field <= 0)
                                throw (new QueryException("Invalid field name specified: '" + attrName + "'"));
                            boolean found = false;
                            for (int j = 1; j < NetFSE.PREDICATES.length; j++) {
                                if (attrValue.startsWith(NetFSE.PREDICATES[j])) {
                                    found = true;
                                    Criteria c = new Criteria();
                                    c.field = NetFSE.getFieldType(attrName);
                                    c.predicate = j;
                                    c.value1 = attrValue.substring(NetFSE.PREDICATES[j].length());
                                    query.addCriteria(c);
                                    //System.out.println("Added criteria");
                                    break;
                                }
                            }
                            if (!found) { // Could be PREDICATE_EQUAL_TO or
                                // PREDICATE_RANGE
                                int tilLoc = attrValue.indexOf("~");
                                if (tilLoc > 0) { // PREDICATE_RANGE
                                    Criteria c = new Criteria();
                                    c.field = NetFSE.getFieldType(attrName);
                                    c.predicate = NetFSE.PREDICATE_RANGE;
                                    c.value1 = attrValue.substring(0, tilLoc);
                                    c.value2 = attrValue.substring(tilLoc + 1);
                                    query.addCriteria(c);
                                } else { // PREDICATE_EQUAL_TO
                                    Criteria c = new Criteria();
                                    c.field = NetFSE.getFieldType(attrName);
                                    c.predicate = NetFSE.PREDICATE_EQUAL_TO;
                                    c.value1 = attrValue.substring(NetFSE.PREDICATES[NetFSE.PREDICATE_EQUAL_TO]
                                            .length() - 1);
                                    query.addCriteria(c);
                                }
                            }
                        }
                    }
                    if (!hadID) {
                        query.setReply(true);
                    }
                    Data data = new Data();
                    data.type = type;
                    data.sensor = sensor;
                    query.setData(data);
                } else if (qName.compareTo("Join") == 0) {
                    //query.newJoin();
                } else if (qName.compareTo("SearchField") == 0) {
                    Criteria c = new Criteria();
                    for (int i = 0; i < atts.getLength(); i++) {
                        String attrName = atts.getLocalName(i);
                        String attrValue = atts.getValue(i);
                        if (attrName.compareTo("name") == 0) {
                            for (int x = 0; x < NetFSE.FIELD_NAMES.length; x++) {
                                if (NetFSE.FIELD_NAMES[x].toUpperCase().compareTo(attrValue.toUpperCase()) == 0) {
                                    c.field = x;
                                    break;
                                }
                            }
                        } else if (attrName.compareTo("predicate") == 0) {
                            for (int x = 0; x < NetFSE.PREDICATES.length; x++) {
                                if (NetFSE.PREDICATES[x].compareTo(attrValue) == 0) {
                                    c.predicate = x;
                                    break;
                                }
                            }
                        } else if (attrName.compareTo("value") == 0) {
                            c.value1 = attrValue;
                        } else if (attrName.compareTo("lower") == 0) {
                            c.value1 = attrValue;
                        } else if (attrName.compareTo("upper") == 0) {
                            c.value2 = attrValue;
                        }
                    }
                    query.addCriteria(c);
                

                } else if (qName.compareTo("Data") == 0) {
                    // Types of data to be returned
                    Data type = new Data();
                    for (int i = 0; i < atts.getLength(); i++) {
                        String attrName = atts.getLocalName(i);
                        String attrValue = atts.getValue(i);
                        if (attrName.compareTo("type") == 0) {
                            type.type = Integer.parseInt(attrValue);
                        } else if (attrName.compareTo("sensor") == 0) {
                            type.sensor = Integer.parseInt(attrValue);
                        }
                        else if (attrName.compareTo("limit") == 0) {
                            type.limit = Integer.parseInt(attrValue);
                        }
                    }
                    query.addData(type);
                    System.out.println(type.toXML());
                } /*
                   * else if (qName.compareTo("Time") == 0) { TimeRange tr =
                   * null; boolean valid = true; for (int i = 0; i <
                   * atts.getLength(); i++) { String attrName =
                   * atts.getLocalName(i); String attrValue = atts.getValue(i);
                   * if (attrName.compareTo("start") == 0) { tr = new
                   * TimeRange(Time.processTime(attrValue)); valid = true; }
                   * else if (attrName.compareTo("end") == 0) {
                   * tr.setEndTime(Time.processTime(attrValue)); valid = true; } }
                   * if (!valid) throw (new QueryException( "Invalid Time
                   * element: must contain either a 'start' or 'end' attribute
                   * of the form 24HH:MM:SS")); if (tr.getEndTime() == null)
                   * tr.setEndTime(Time.processTime("23:59:59")); if
                   * (tr.getStartTime() == null)
                   * tr.setStartTime(Time.processTime("0:0:0"));
                   * query.addTimeRange(tr); } else if (qName.compareTo("Date") ==
                   * 0) { DateRange dr = null; for (int i = 0; i <
                   * atts.getLength(); i++) { String attrName =
                   * atts.getLocalName(i); String attrValue = atts.getValue(i);
                   * if (attrName.compareTo("start") == 0) dr = new
                   * DateRange(Date.processDate(attrValue)); else if
                   * (attrName.compareTo("end") == 0)
                   * dr.setEndDate(Date.processDate(attrValue)); }
                   * query.addDateRange(dr); }
                   */else if (qName.compareTo("Timestamp") == 0) {
                    TimestampRange tsr = null;
                    String startTS = null;
                    String endTS = null;

                    for (int i = 0; i < atts.getLength(); i++) {
                        String attrName = atts.getLocalName(i);
                        String attrValue = atts.getValue(i);
                        if (attrName.compareTo("start") == 0)
                            startTS = attrValue;
                        else if (attrName.compareTo("end") == 0)
                            endTS = attrValue;
                    }

                    //if (startTS == null)
                    //    startTS = endTS;
                    //if (endTS == null)
                    //    endTS = startTS;
                    if (startTS == null)
                        throw (new QueryException("Missing values in Timestamp attribute."));
                    tsr = TimestampRange.generate(startTS, endTS);
                    if (tsr != null) {
                        query.addTimestampRange(tsr);
                        System.out.println("ADDED TSR: " + tsr.toXML());
                    }
                } else if (qName.compareTo("MarshalledResult") == 0) {
                    MarshalledResult result = new MarshalledResult();
                    for (int i = 0; i < atts.getLength(); i++) {
                        String attrName = atts.getLocalName(i);
                        String attrValue = atts.getValue(i);
                        if (attrName.compareTo("host") == 0)
                            result.host = attrValue;
                        if (attrName.compareTo("port") == 0)
                            result.port = Integer.parseInt(attrValue);
                        if (attrName.compareTo("query") == 0)
                            result.queryID = Integer.parseInt(attrValue);
                    }
                    if (result.queryID == -1) {
                        throw (new QueryException("Invalid marshalled result specified. Query ID was -1."));
                    }
                    query.setResult(result);
                } else if (qName.compareTo("SocketResult") == 0) {
                    SocketResult result = new SocketResult();
                    for (int i = 0; i < atts.getLength(); i++) {
                        String attrName = atts.getLocalName(i);
                        String attrValue = atts.getValue(i);
                        if (attrName.compareTo("host") == 0)
                            result.host = attrValue;
                        if (attrName.compareTo("port") == 0)
                            result.port = Integer.parseInt(attrValue);
                    }
                    query.setResult(result);
                } else if (qName.equals("DatabaseResult") || qName.equals("DBResult")) {
                    DatabaseResult result = new DatabaseResult();
                    for (int i = 0; i < atts.getLength(); i++) {
                        String attrName = atts.getLocalName(i);
                        String attrValue = atts.getValue(i);
                        if (attrName.toUpperCase().compareTo("REPLYTO") == 0)
                            result.replyTo = attrValue;
                    }
                    query.setResult(result);
                } else if (qName.equals("DBResultEntry") || qName.equals("DatabaseResultEntry")) {
                    DatabaseResultEntry result = new DatabaseResultEntry();
                    String typeName = "";
                    int type = -1;
                    for (int i = 0; i < atts.getLength(); i++) {
                        String attrName = atts.getLocalName(i);
                        String attrValue = atts.getValue(i);
                        if (attrName.compareTo("host") == 0)
                            result.host = attrValue;
                        else if (attrName.compareTo("database") == 0)
                            result.database = attrValue;
                        else if (attrName.compareTo("table") == 0)
                            result.table = attrValue;
                        else if (attrName.compareTo("user") == 0)
                            result.user = attrValue;
                        else if (attrName.compareTo("password") == 0)
                            result.password = attrValue;
                        else if (attrName.compareTo("expire") == 0)
                            result.expire = Integer.parseInt(attrValue);
                        else if (attrName.compareTo("dbtype") == 0) {
                            /*if (attrValue.toUpperCase().equals("MYSQL")) {
                                result.dbType = NetFSE.DB_TYPE_MYSQL;
                                result.port = NetFSE.DB_PORT_MYSQL;
                            } else if (attrValue.toUpperCase().equals("ORACLE")) {
                                result.dbType = NetFSE.DB_TYPE_ORACLE;
                                result.port = NetFSE.DB_PORT_ORACLE;
                            } else if (attrValue.toUpperCase().equals("PSQL")) {
                                result.dbType = NetFSE.DB_TYPE_PSQL;
                                result.port = NetFSE.DB_PORT_PSQL;
                            } else if (attrValue.toUpperCase().startsWith("POSTGRES")) {
                                result.dbType = NetFSE.DB_TYPE_PSQL;
                                result.port = NetFSE.DB_PORT_PSQL;
                            } else {
                                throw (new QueryException("Invalid database type specified: '" + attrValue + "'."));
                            }*/
                        } else if (attrName.compareTo("type") == 0) {
                            typeName = attrValue;
                            try {
                                type = Integer.parseInt(typeName);
                            } catch (NumberFormatException nme) {
                                // no op
                            }
                        } else if (attrName.compareTo("port") == 0)
                            result.port = Integer.parseInt(attrValue);
                    }
                    if (type == -1) {
                        String[] typeNames = NetFSE.getTypeNames();
                        for (int i = 0; i <  typeNames.length; i++) {
                            if (typeNames[i].toUpperCase().compareTo(typeName.toUpperCase()) == 0) {
                                type = i;
                                break;
                            }
                        }
                    }
                    if ((type == -1) && (typeName.length() > 0)) {
                        throw (new QueryException("Invalid type specified: '" + typeName + "'."));
                    }
                    result.type = type;
                    if (query.getResult() == null) {
                        DatabaseResult dbr = new DatabaseResult();
                        dbr.addEntry(result);
                        query.setResult(dbr);
                    } else {
                        DatabaseResult dbr = (DatabaseResult) query.getResult();
                        dbr.addEntry(result);
                    }
                }
                
            } else {
                //System.out.println("Start element: {" + uri + "}" + name);
            }

        } catch (Exception E) {
            E.printStackTrace();
            foundError = true;
        }
    }

    public void endElement(String uri, String name, String qName) {
        try {
            if ("".equals(uri)) {
                //System.out.println("End element: " + qName);
                if (qName.compareTo("Query") == 0) {
                    if (resultFields.size() > 0) {
                        ResultField[] resultFields2 = new ResultField[resultFields.size()];
                        for (int i = 0; i < resultFields2.length; i++)
                            resultFields2[i] = (ResultField) resultFields.elementAt(i);
                        
                        resultFields.removeAllElements();
                    }
                    if (query.hasParent()) {
                        Query parent = query.getParent();
                        //if (!parent.joinPending())
                        parent.addSubquery(query);
                        //else
                        // parent.addToJoin(query);
                        query = query.getParent();
                    }
                }
                //else if (qName.compareTo("Join") == 0) {
                //  if (query.joinPending())
                //    query.closeJoin();
                //}
            } else {
                //System.out.println("End element: {" + uri + "}" + name);
            }
        } catch (Exception E) {
            E.printStackTrace();
            foundError = true;
        }
    }

    public void characters(char ch[], int start, int length) {
        System.out.print("Characters:    \"");
        for (int i = start; i < start + length; i++) {
            switch (ch[i]) {
            /*
             * case '\\': System.out.print("\\\\"); break; case '"':
             * System.out.print("\\\""); break; case '\n':
             * System.out.print("\\n"); break; case '\r':
             * System.out.print("\\r"); break; case '\t':
             * System.out.print("\\t"); break;
             */
            default:
                System.out.print(ch[i]);
                break;
            }
        }
        System.out.print("\"\n");
    }

}